package com.homelink.crashhandle;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import com.homelink.crashhandle.util.ConstantsUtils;
import com.homelink.crashhandle.util.FileUtils;
import com.homelink.crashhandle.util.ZipUtils;

import java.io.*;
import java.util.Date;

/**
 * Created by Fcy on 2015/10/20.<br/>
 */
public class LogBuffer
{
	private Context mContext;

	private static LogBuffer mInstance;

	static boolean initialized = false;

	private static final String KEY_POINT = "log_point";

	private LimitArrayList<String> mData = new LimitArrayList<String>();

	/**
	 * 当前写入的文件
	 */
	private int point;

	/**
	 * 日志格式，形如：[2010-01-22 13:39:1][com.a.c]error occured
	 **/
	private final static String LOG_ENTRY_FORMAT = "[%tF %tT][%s]%s";

	public final static long ONE_M = 1024 * 1024; // 1M
	public final static long ONE_K = 1024; // 1K

	/**
	 * 每个log文件大小
	 */
	private long logSize; // 字节

	/**
	 * 保存的log文件个数
	 */
	private int logFileCount;

	private SharedPreferences sp;
	private SharedPreferences.Editor editor;

	private PrintStream logStream;

	private LogBuffer()
	{
	}

	public static LogBuffer getInstance()
	{
		if (mInstance == null)
		{
			synchronized (LogBuffer.class)
			{
				if (mInstance == null)
				{
					mInstance = new LogBuffer();
				}
			}
		}
		return mInstance;
	}

	public void initOptions(Context context)
	{
		initOptions(context, 5, 1000, ONE_M);
	}

	/**
	 * 初始化 log最大个数以及 log文件大小
	 *
	 * @param context
	 * @param logFileCount
	 *            log文件个数
	 * @param logCount
	 *            log最大个数即list最大个数
	 * @param logSize
	 *            log文件大小
	 */
	public void initOptions(Context context, int logFileCount, int logCount,
	        long logSize)
	{
		if (logFileCount < 2)
		{
			logFileCount = 2;
		}
		if (logCount < 1)
		{
			logCount = 100;
		}
		if (logSize < 1)
		{
			logSize = ONE_K;
		}

		mContext = context;

		this.logFileCount = logFileCount;
		setLogCount(logCount);
		setLogSize(logSize);

		sp = context.getSharedPreferences(ConstantsUtils.HOMELINK_LOG,
		        Context.MODE_PRIVATE);
		editor = sp.edit();
		// editor.commit();
		getPoint();
	}

	// public int getLogFileCount()
	// {
	// return logFileCount;
	// }
	//
	// public void setLogFileCount(int logFileCount)
	// {
	// if (logFileCount > 0)
	// {
	// this.logFileCount = logFileCount;
	// }
	// else
	// {
	// this.logFileCount = 2;
	// }
	// }

	/**
	 * 设置最大存储log数量，超过将会覆盖，
	 *
	 * @param max
	 *            最大存储log数量
	 */
	private void setLogCount(int max)
	{
		if (mData != null)
		{
			mData.setMax(max);
		}
	}

	/**
	 * 设置每个log文件的最大大小
	 *
	 * @param logSize
	 */
	private void setLogSize(long logSize)
	{
		this.logSize = logSize;
	}

	private String getCurrTime()
	{
		Date now = new Date();
		return String.format("[%tF %tT]", now, now);
	}

	/**
	 * 添加log信息
	 *
	 * @param log
	 *            log信息
	 * @param tag
	 *            Activity、Fragment、Adapter等
	 */
	public void log(String tag, String log)
	{
		Date now = new Date();
		/** 日志格式，形如：[2010-01-22 13:39:1][包名][tag]log信息 **/
		String logs = String.format(LOG_ENTRY_FORMAT, now, now, tag, log);
		mData.add(logs);
	}

	private void setPoint(int point)
	{
		if (editor == null)
		{
			throw new IllegalArgumentException("必须调用initOptions");
		}
		if (point >= logFileCount)
			return;

		this.point = point;
		// 存到sp中
		editor.putInt(KEY_POINT, point);
		editor.commit();
	}

	private void getPoint()
	{
		if (sp == null)
		{
			throw new IllegalArgumentException("必须调用initOptions");
		}
		point = sp.getInt(KEY_POINT, 0);
	}

	/**
	 * 初始化创建logFileCount个文件作为缓冲，分别为0,1,2,3,4 .log
	 */
	private void init()
	{
		if (initialized)
			return;

		if (FileUtils.getSdRoot() == null)
			return;

		for (int i = 0; i < logFileCount; i++)
		{
			getLogFile(i);
		}
		getPoint();

		try
		{
			logStream = new PrintStream(
			        new FileOutputStream(getLogFile(point), true), true);
			initialized = true;
		} catch (FileNotFoundException e)
		{
			e.printStackTrace();
		}

	}

	private void closeStream()
	{
		if (logStream != null)
		{
			logStream.close();
			initialized = false;
		}
	}

	/**
	 * crash情况下，将log日志压缩为zip包
	 */
	public void post()
	{
		post(true);
	}

	/**
	 * 上传操作，先进行打zip包，然后对zip包进行切片，然后push
	 * 
	 * @param isCrash
	 */
	public void post(boolean isCrash)
	{
		if (FileUtils.getSdRoot() == null)
			return;

		// crash, 将对应包名打成zip放到 sd/homelink_logs/log_push目录下
		File zipDir = FileUtils.getPushDir();
		// app名_手机类型(android,ios)_账号_时间_日志类型（crash，常规？）_deviceid
		// 0代表android，1代表ios
		// 0代表crash，1代表非crash

		// HashMap<String, String> map = DeviceUtils.obtainSimpleInfo(mContext);

		String desName = FileUtils.getLogZipName(mContext, isCrash);
		File logDir = isCrash ? FileUtils.getPackageLogDir(mContext)
		        : FileUtils.getLogFilesDir();
		try
		{
			File zipFile = new File(zipDir, desName);
			ZipUtils.zip(logDir, zipFile);
			// 压缩成功，则删除所有logs文件，指针归零
			FileUtils.deleteContents(logDir);
			setPoint(0);
			// 切分文件
			FileUtils.split(zipFile);
		} catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	// /**
	// * 将log日志压缩为zip包
	// *
	// * @param isCrash
	// * 是否是crash
	// */
	// public void zip2(boolean isCrash)
	// {
	// if (getLogDir() != null)
	// {
	// File zipDir = FileUtils.getPushDir();
	// // app名_手机类型(android,ios)_账号_时间_日志类型（crash，常规？）_deviceid
	// // 0代表android，1代表ios
	// // 0代表crash，1代表非crash
	//
	// HashMap<String, String> map = DeviceUtils
	// .obtainSimpleInfo(mContext);
	//
	// String desName;
	// if (isCrash)
	// {
	// desName = mContext.getPackageName() + "_0_" + "123_"
	// + System.currentTimeMillis() + "_0_"
	// + DeviceUtils.getUUID(mContext) + ".zip";
	// LogUtils.e("LogBuffer", "----------------");
	//
	// // desName = mContext.getPackageName() + "_"
	// // + map.get("versionName") + "_android_"
	// // + map.get("MODEL") + "_" + DeviceUtils.getUUID(mContext)
	// // + "_iscrash_" + getCurrTime() + ".zip";
	//
	// try
	// {
	// ZipUtils.zip(getLogDir(), new File(zipDir, desName));
	// // 压缩成功，则删除所有logs文件，指针归零
	// FileUtils.deleteContents(getLogDir());
	// setPoint(0);
	//
	// } catch (IOException e)
	// {
	// e.printStackTrace();
	// }
	// }
	// else
	// {
	// // desName = mContext.getPackageName() + "_"
	// // + map.get("versionName") + "_android_"
	// // + map.get("MODEL") + DeviceUtils.getUUID(mContext) + "_"
	// // + getCurrTime() + ".zip";
	// // 打zip包
	// // ZipUtils.zip(getLogDir(), new File(zipDir, desName));
	// File logDir = FileUtils.getLogDir(); // sd/homelink_logs/
	// if (logDir != null && logDir.exists() && logDir.isDirectory())
	// {
	// File[] files = logDir.listFiles();
	// for (File file : files)
	// {
	// desName = mContext.getPackageName() + "_0_" + "123_"
	// + System.currentTimeMillis() + "_1_"
	// + DeviceUtils.getUUID(mContext) + ".zip";
	// try
	// { // 将每个logs文件夹打包到zip文件夹下
	// ZipUtils.zip(new File(file, "logs"),
	// new File(new File(file, "zip"), desName));
	// } catch (IOException e)
	// {
	// e.printStackTrace();
	// }
	// }
	// }
	//
	// // 用户主动上传
	// List<File> allFiles = new ArrayList<>();
	// FileUtils.getAllFiles(allFiles, FileUtils.getLogDir(), ".zip");
	// }
	// }
	// }

	public void writeLog()
	{
		writeLog(true);
	}

	/**
	 * 将log写到文件中
	 *
	 * @param isCrash
	 *            是否发生crash，如果是则写入sd卡后清空 mData数据
	 */
	public void writeLog(boolean isCrash)
	{
		// 每次写入文件时都要判断，因为文件有可能被删除，需要重新创建
		if (!initialized)
			init();
		if (logStream == null || logStream.checkError())
		{
			initialized = false;
			return;
		}
		// 将内存中的mData写到sd卡中的log文件中
		// 如果下一个文件>logSize，判断已经被写过，那么先清空该文件再写，
		// 1.获取当前操作的文件
		File currFile = getLogFile(point);
		if (currFile == null)
			return;
		getPoint();

		// 2.写之前要判断写入下一条后是否>logSize，如果>logSize,则stream指向下一个文件
		if (mData.getRealIndex() >= mData.getMax())
		{ // 写满覆盖
			for (int i = mData.getIndex() + 1; i < mData.getMax(); i++)
			{
				String log = mData.get(i);
				if (log.getBytes().length + currFile.length() > logSize)
				{
					// 超过logSize，不写入，stream指向下个文件
					resetLogStream();
					currFile = getLogFile(point);
				}
				// 写入该条log信息
				logStream.println(log);
			}
		}

		for (int i = 0; i <= mData.getIndex(); i++)
		{
			String log = mData.get(i);
			if (log.getBytes().length + currFile.length() > logSize)
			{
				resetLogStream();
				currFile = getLogFile(point);
			}
			logStream.println(log);
		}
		closeStream();

		if (isCrash)
			clearData();
	}

	/**
	 * 写数据
	 *
	 * @param msg
	 */
	public void write(String msg)
	{
		if (!initialized)
			init();
		if (logStream == null || logStream.checkError())
		{
			initialized = false;
			return;
		}
		File currFile = getLogFile(point);
		if (currFile == null)
			return;
		if (msg.getBytes().length + currFile.length() > logSize)
		{ // 超过logSize，不写入，stream指向下个文件
			resetLogStream();
			// currFile = getLogFile(point);
		}

		msg = getCurrTime() + msg;
		logStream.println(msg);
		closeStream();
	}

	/**
	 * 用户要上传log，则发送广播，告诉需要每个运行的app都进行写文件
	 */
	public void send()
	{
		Intent intent = new Intent();
		intent.setAction(ConstantsUtils.BROAD_CAST_ACTION_PUSH);
		mContext.sendBroadcast(intent);
	}

	/**
	 * 清空某个文件
	 *
	 * @param file
	 */
	private void clearFile(File file)
	{
		try
		{
			FileOutputStream fos = new FileOutputStream(file, false);
			fos.write("".getBytes());
			fos.close();

		} catch (IOException e)
		{
			e.printStackTrace();
		}

	}

	/**
	 * 将logStream指向新的log文件
	 */
	private void resetLogStream()
	{
		point = (point + 1) % logFileCount;
		setPoint(point);
		File logFile = getLogFile(point);
		if (logFile == null)
		{
			logStream = null;
			return;
		}

		if (logFile.length() > 1)
		{ // 文件已经被写入过，清空文件
			clearFile(logFile);
		}

		try
		{ // 追加、自动刷新
			logStream = new PrintStream(new FileOutputStream(logFile, true),
			        true);

		} catch (FileNotFoundException e)
		{
			e.printStackTrace();
		}
	}

	private void clearData()
	{
		mData.clear();
	}

	/**
	 * 获取对应log文件
	 *
	 * @param i
	 * @return
	 */
	private File getLogFile(int i)
	{
		if (i >= logFileCount)
			return null;
		File logDir = FileUtils.getPackageLogDir(mContext);
		if (logDir == null)
			return null;
		File file = new File(logDir, i + ".log");
		if (!file.exists())
			try
			{
				// 如果文件不存在，指针归零，是否要清空所有已存在的文件内容
				setPoint(0);
				file.createNewFile();
			} catch (IOException e)
			{
				e.printStackTrace();
			}
		return file;
	}
}
